#include "pch.h"
#include "MainPage.xaml.h"
#include "GameEngine.h"
#include "Rendering/GameRenderer.h"
#include "GameObjects/Sphere.h"
#include "GameObjects/Cube.h"
#include "GameObjects/Face.h"
#include "GameObjects/WallTile.h"
#include "GameObjects/MjpegCamera.h"
#include "GameObjects/Puck.h"
#include "GameObjects/Paddle.h"
#include "GameObjects/WallLight.h"
#include "Utilities/GameTimer.h"
#include "Audio/AudioEngine.h"
#include "GameObjects/GameConstants.h"

using namespace DirectX;
using namespace Microsoft::WRL;
using namespace Windows::Storage;
using namespace Windows::UI::Core;
using namespace Windows::Networking;
using namespace Windows::Networking::Connectivity;
using namespace Windows::Foundation::Collections;
using namespace concurrency;
using namespace Coding4Fun::Maelstrom::Common;
using namespace Coding4Fun::Maelstrom::Communication;
using namespace Coding4Fun::Maelstrom::GameConsole;

GameEngine::GameEngine()
{
	m_gameState = GameState::Initializing;
	srand((unsigned)time(0));
}

void GameEngine::Initialize(_In_ GameRenderer^ renderer, MainPage^ page)
{
	m_renderer = renderer;
	m_page = page;

	m_player = GetPlayer();

	if(m_player == Player::Invalid)
	{
		m_page->SetOutputText(L"I have no idea who I am...make sure the IP addresses in Settings match up with the physical machines!");
		throw Exception::CreateException(E_FAIL, L"I have no idea who I am...make sure the IP addresses in Settings match up with the physical machines!");
	}

	if(m_player == Player::One)
	{
		m_defaultEyePosition = GameConstants::EyeP1;
		m_defaultLookAt = GameConstants::LookAtP1;
	}
	else if(m_player == Player::Two)
	{
		m_defaultEyePosition = GameConstants::EyeP2;
		m_defaultLookAt = GameConstants::LookAtP2;
	}
	
	m_hardware = ref new HardwareCommunication();

	m_audioEngine = ref new AudioEngine();
	m_audioEngine->Initialize();

	m_controllerListener1 = ref new SocketListener();
	m_controllerListener1->Listen(Settings::ControllerPort_Player1);

	m_controllerListener2 = ref new SocketListener();
	m_controllerListener2->Listen(Settings::ControllerPort_Player2);

	if(m_player == Player::One)
	{
		m_consoleListener = ref new SocketListener();
		m_consoleListener->Listen(Settings::GameConsolePort);
	}
	
	// CREATE THE DEFAULT CAMERA
	// -------------------------------------------------------------
	// NOTE:THESE VALUES MAY BE ADJUSTED IN m_renderer
	//		BASED ON THE DEVICE RESOLUTION AND SIZE

	m_camera = ref new Camera;
	m_camera->SetProjParams(XM_PI / 4, 1.777f, 0.01f, 100.0f);
	m_camera->SetViewParams(m_defaultEyePosition, m_defaultLookAt, XMFLOAT3 (0.0f, 1.0f, 0.0f));

	m_timer = ref new GameTimer();
	m_timer->Reset();

	CreateGameObjects();
}

void GameEngine::ConnectToConsole1()
{
	m_consoleClient = ref new SocketClient();
	create_task([this]()
	{
		return m_consoleClient->ConnectAsync(Settings::GameConsoleIp_Player1, Settings::GameConsolePort);
	}).then([=](task<void> t)
	{
		try
		{
			t.get();
			m_page->SetOutputText("Connected to Game Console 1");
		}
		catch(Exception^ ex)
		{
			m_page->SetOutputText(L"Could not connect to Player 1 Game Console!");
		}
	});
}

void GameEngine::CreateGameObjects() 
{
	m_renderObjects = std::vector<GameObject^>();
	m_environmentCollisionWalls = std::vector<WallTile^>();

	// CREATE TEST PUCKS
	for ( int i = 0; i < GameConstants::MaxPucks; i++) 
	{
		auto puck = ref new Puck(XMFLOAT3(0,0,0), 1.0f);
		m_pucks.push_back(puck);
		m_renderObjects.push_back(puck);
	}

	if(m_player == Player::One)
	{
		// CREATE FACE FOR OPPONENT VIDEO
		m_player2Camera = ref new MjpegCamera(m_renderer->Loader());
		m_player2Camera->SetPlane(XMFLOAT3(0.0f, 0.0f, 0.0f), XMFLOAT3(0.0f, GameConstants::MaxBound.x, 0.0f), XMFLOAT3(0.0f, GameConstants::MaxBound.x * 0.625f, 0.0f));
		m_player2Camera->Position(XMFLOAT3(0.0f, 0.0f, GameConstants::MaxBound.z - 1.0f));
		m_player2Camera->Start(Settings::CameraUriLeft_Player2, Settings::CameraUriRight_Player2);
		m_renderObjects.push_back(m_player2Camera);
	}
	else if(m_player == Player::Two)
	{
		// CREATE FACE FOR OPPONENT VIDEO
		m_player1Camera = ref new MjpegCamera(m_renderer->Loader());
		m_player1Camera->SetPlane(XMFLOAT3(0.0f, 0.0f, 0.0f), XMFLOAT3(0.0f, GameConstants::MaxBound.x, 0.0f), XMFLOAT3(0.0f, GameConstants::MaxBound.x * 0.625f, 0.0f));
		m_player1Camera->Position(XMFLOAT3(0.0f, 0.0f, GameConstants::MinBound.z + 1.0f));
		m_player1Camera->Start(Settings::CameraUriLeft_Player1, Settings::CameraUriRight_Player1);
		m_renderObjects.push_back(m_player1Camera);
	}

	// CREATE NON RENDERING WALL TILES FOR COLLISION DETECTION. 
	// EACH TILE REPRESENTS AN ENTIRE WALL FACE.
	WallTile^ ceilPanel = ref new WallTile(XMFLOAT3(0.0f, 0.0f ,0.0f), XMFLOAT3(15.0f, 0.0f, 0.0f), XMFLOAT3(0.0f, 0.0f, GameConstants::MaxBound.z));
	ceilPanel->Position(XMFLOAT3(2.0f, 10.0f, GameConstants::MinBound.z));
	m_environmentCollisionWalls.push_back(ceilPanel);

	WallTile^ leftPanelTop = ref new WallTile(XMFLOAT3(0.0f, 8.0f ,0.0f), XMFLOAT3(2.0f, 10.0f, 0.0f), XMFLOAT3(0.0f, 8.0f, GameConstants::MaxBound.z));
	leftPanelTop->Position(XMFLOAT3(0.0f, 8.0f, 0.0f));
	m_environmentCollisionWalls.push_back(leftPanelTop);

	WallTile^ leftPanelSide = ref new WallTile(XMFLOAT3(0.0f, 8.0f ,0.0f), XMFLOAT3(0.0f, 2.0f, 0.0f), XMFLOAT3(0.0f, 8.0f, -GameConstants::MaxBound.z));
	leftPanelSide->Position(XMFLOAT3(0.0f, 8.0f, GameConstants::MaxBound.z));
	m_environmentCollisionWalls.push_back(leftPanelSide);

	WallTile^ leftPanelBottom = ref new WallTile(XMFLOAT3(0.0f, 2.0f ,0.0f), XMFLOAT3(2.0f, 0.0f, 0.0f), XMFLOAT3(0.0f, 2.0f, -GameConstants::MaxBound.z));
	leftPanelBottom->Position(XMFLOAT3(0.0f, 2.0f, GameConstants::MaxBound.z));
	m_environmentCollisionWalls.push_back(leftPanelBottom);

	WallTile^ floorPanel = ref new WallTile(XMFLOAT3(0.0f, 0.0f ,0.0f), XMFLOAT3(15.0, 0.0f, 0.0f), XMFLOAT3(0.0f, 0.0f, -GameConstants::MaxBound.z));
	floorPanel->Position(XMFLOAT3(2.0f, 0.0f, GameConstants::MaxBound.z));
	m_environmentCollisionWalls.push_back(floorPanel);

	WallTile^ rightPanelBottom = ref new WallTile(XMFLOAT3(17.0f, 0.0f ,0.0f), XMFLOAT3(19.0f, 2.0f, 0.0f), XMFLOAT3(17.0f, 0.0f, -GameConstants::MaxBound.z));
	rightPanelBottom->Position(XMFLOAT3(17.0f, 0.0f, GameConstants::MaxBound.z));
	m_environmentCollisionWalls.push_back(rightPanelBottom);

	WallTile^ rightPanelSide = ref new WallTile(XMFLOAT3(19.0f, 2.0f ,0.0f), XMFLOAT3(19.0f, 8.0f, 0.0f), XMFLOAT3(19.0f, 0.0f, -GameConstants::MaxBound.z));
	rightPanelSide->Position(XMFLOAT3(19.0f, 2.0f, GameConstants::MaxBound.z));
	m_environmentCollisionWalls.push_back(rightPanelSide);

	WallTile^ rightPanelTop = ref new WallTile(XMFLOAT3(17.0f, 10.0f ,0.0f), XMFLOAT3(19.0f, 8.0f, 0.0f), XMFLOAT3(19.0f, 10.0f, GameConstants::MaxBound.z));
	rightPanelTop->Position(XMFLOAT3(17.0f, 10.0f, GameConstants::MinBound.z));
	m_environmentCollisionWalls.push_back(rightPanelTop);



	// CREATE SMALLER WALLTILES TO RENDER TO VISUALLY SEPARATE EACH PANEL INTO PLATES
	for(int Plate = 0; Plate < GameConstants::NumWallSegments; Plate++) 
	{
		std::vector<WallLight^> lights;
		std::vector<WallTile^> tiles;

		float z = GameConstants::WallSegmentDepth * Plate;


		/* wall tiles
		   ----------
			   0
			  ---  
		  1  /   \ 7
		  2  |   | 6
		  3	 \   / 5
			  ---  	
			   4		*/


		WallTile^ tile;

		// Top
		tile = ref new WallTile(XMFLOAT3(0.0f, 0.0f ,0.0f), XMFLOAT3(15.0f, 0.0f, 0.0f), XMFLOAT3(0.0f, 0.0f, GameConstants::WallSegmentDepth));
		tile->Position(XMFLOAT3(2.0f, 10.0f, z));
		m_renderObjects.push_back(tile);
		tiles.push_back(tile);

		// upper left
		tile = ref new WallTile(XMFLOAT3(0.0f, 8.0f ,0.0f), XMFLOAT3(2.0f, 10.0f, 0.0f), XMFLOAT3(0.0f, 8.0f, GameConstants::WallSegmentDepth));
		tile->Position(XMFLOAT3(0.0f, 8.0f, z));
		tile->Corner(true);
		m_renderObjects.push_back(tile);
		tiles.push_back(tile);

		// middle left
		tile = ref new WallTile(XMFLOAT3(0.0f, 2.0f ,0.0f), XMFLOAT3(0.0f, 8.0f, 0.0f), XMFLOAT3(0.0f, 2.0f, GameConstants::WallSegmentDepth));
		tile->Position(XMFLOAT3(0.0f, 2.0f, z));
		m_renderObjects.push_back(tile);
		tiles.push_back(tile);

		// lower left
		tile = ref new WallTile(XMFLOAT3(0.0f, 2.0f ,0.0f), XMFLOAT3(2.0f, 0.0f, 0.0f), XMFLOAT3(0.0f, 2.0f, -GameConstants::WallSegmentDepth));
		tile->Position(XMFLOAT3(0.0f, 2.0f, 10.0f + z));
		tile->Corner(true);
		m_renderObjects.push_back(tile);
		tiles.push_back(tile);

		// Bottom
		tile = ref new WallTile(XMFLOAT3(0.0f, 0.0f ,0.0f), XMFLOAT3(15.0, 0.0f, 0.0f), XMFLOAT3(0.0f, 0.0f, -GameConstants::WallSegmentDepth));
		tile->Position(XMFLOAT3(2.0f, 0.0f, 10.0f + z));
		m_renderObjects.push_back(tile);
		tiles.push_back(tile);

		// lower right
		tile = ref new WallTile(XMFLOAT3(17.0f, 0.0f ,0.0f), XMFLOAT3(19.0f, 2.0f, 0.0f), XMFLOAT3(17.0f, 0.0f, -GameConstants::WallSegmentDepth));
		tile->Position(XMFLOAT3(17.0f, 0.0f, 10.0f + z));
		tile->Corner(true);
		m_renderObjects.push_back(tile);
		tiles.push_back(tile);

		// middle right
		tile = ref new WallTile(XMFLOAT3(19.0f, 2.0f ,0.0f), XMFLOAT3(19.0f, 8.0f, 0.0f), XMFLOAT3(19.0f, 2.0f, GameConstants::WallSegmentDepth));
		tile->Position(XMFLOAT3(19.0f, 2.0f, z));
		m_renderObjects.push_back(tile);
		tiles.push_back(tile);

		// upper right
		tile = ref new WallTile(XMFLOAT3(17.0f, 10.0f ,0.0f), XMFLOAT3(19.0f, 8.0f, 0.0f), XMFLOAT3(17.0f, 10.0f, GameConstants::WallSegmentDepth));
		tile->Position(XMFLOAT3(17.0f, 10.0f, z));
		tile->Corner(true);
		m_renderObjects.push_back(tile);
		tiles.push_back(tile);

		m_wallTiles.push_back(tiles);



		WallLight^ wallLight;

		/* wall light indexes
		   ------------------
		
		  0   ---  7
		  1  /   \ 6
			 |   |
		  2	 \   / 5
		  3   ---  4	*/

		// CREATE WALL LIGHTS (LEFT)
		wallLight = ref new WallLight(XMFLOAT3(2.0f, 10.0f, z), 0.1f, XMFLOAT3(0.0f, 0.0f, GameConstants::LightDepth));
		lights.push_back(wallLight);
		m_renderObjects.push_back(wallLight);

		wallLight = ref new WallLight(XMFLOAT3(0.0f, 8.0f, z), 0.1f, XMFLOAT3(0.0f, 0.0f, GameConstants::LightDepth));
		lights.push_back(wallLight);
		m_renderObjects.push_back(wallLight);

		wallLight = ref new WallLight(XMFLOAT3(0.0f, 2.0f, z), 0.1f, XMFLOAT3(0.0f, 0.0f, GameConstants::LightDepth));
		lights.push_back(wallLight);
		m_renderObjects.push_back(wallLight);

		wallLight = ref new WallLight(XMFLOAT3(2.0f, 0.0f, z), 0.1f, XMFLOAT3(0.0f, 0.0f, GameConstants::LightDepth));
		lights.push_back(wallLight);
		m_renderObjects.push_back(wallLight);

		// CREATE WALL LIGHTS (RIGHT)
		wallLight = ref new WallLight(XMFLOAT3(17.0f, 0.0f, z), 0.1f, XMFLOAT3(0.0f, 0.0f, GameConstants::LightDepth));
		lights.push_back(wallLight);
		m_renderObjects.push_back(wallLight);

		wallLight = ref new WallLight(XMFLOAT3(19.0f, 2.0f, z), 0.1f, XMFLOAT3(0.0f, 0.0f, GameConstants::LightDepth));
		lights.push_back(wallLight);
		m_renderObjects.push_back(wallLight);

		wallLight = ref new WallLight(XMFLOAT3(19.0f, 8.0f, z), 0.1f, XMFLOAT3(0.0f, 0.0f, GameConstants::LightDepth));
		lights.push_back(wallLight);
		m_renderObjects.push_back(wallLight);

		wallLight = ref new WallLight(XMFLOAT3(17.0f, 10.0f, z), 0.1f, XMFLOAT3(0.0f, 0.0f, GameConstants::LightDepth));
		lights.push_back(wallLight);
		m_renderObjects.push_back(wallLight);

		m_wallLights.push_back(lights);
	}

	// CREATE PADDLE FOR PLAYER1
	m_paddle1 = ref new Paddle();
	m_paddle1->Position(XMFLOAT3(m_defaultEyePosition.x, m_defaultEyePosition.y, GameConstants::Paddle1Position));
	m_paddle1->NormalizedPosition(XMFLOAT3(0.5f, 0.5f, m_paddle1->Position().z));
	m_paddle1->Normal(XMFLOAT3(0.0f, 0.0f, 1.0f));

	// CREATE PADDLE FOR PLAYER2
	m_paddle2 = ref new Paddle();
	m_paddle2->Position(XMFLOAT3(m_defaultEyePosition.x, m_defaultEyePosition.y, GameConstants::Paddle2Position));
	m_paddle2->NormalizedPosition(XMFLOAT3(0.5f, 0.5f, m_paddle2->Position().z));
	m_paddle2->Normal(XMFLOAT3(0.0f, 0.0f, -1.0f));

	// order these so the alpha draws properly
	if(m_player == Player::One)
	{
		m_transparentRenderObjects.push_back(m_paddle2);
		m_transparentRenderObjects.push_back(m_paddle1);
	}
	else if(m_player == Player::Two)
	{
		m_transparentRenderObjects.push_back(m_paddle1);
		m_transparentRenderObjects.push_back(m_paddle2);
	}

	m_loaded = true;
}

void GameEngine::ResetGame()
{
	m_damageTimer = -1.0f;

	m_player1Score = 0;
	m_player2Score = 0;
	m_powerupTimer = -GameConstants::Powerup::Split::Time;
	m_splitActive = false;
	m_winner = Player::Invalid;

	m_countDownStep = 4;

	m_player1Ready = false;
	m_player2Ready = false;

	m_paddle1->Active(false);
	m_paddle2->Active(false);

	m_timer->Reset();

	ClearWallLights();
	ClearWallTiles();
	TurnOffAllCountdownLights();

	ResetPucks();
}

void GameEngine::GameTick()
{
	if(!m_loaded)
		return;

	m_timer->Update();

	HandleNetwork();

	// ANIMATE FUNCTIONS BASED ON STATE
	switch (m_gameState)
	{
		case GameState::Initializing:
			break;
		case GameState::WaitingRoom:
			if(m_player1Ready && m_player2Ready)
				SetGameState(GameState::Countdown);
			break;
		case GameState::Countdown:
			HandleCountdown();
			break;
		case GameState::GamePlaying:
			HandleGamePlaying();
			break;
		case GameState::GameResults:
			HandleGameResults();

			if(m_timer->PlayingTime() - m_resetTimer > GameConstants::ResetTimer)
				SetGameState(GameState::WaitingRoom);

			if(m_player1Score > m_player2Score)
				m_page->SetOutputText("GAME OVER - P1 wins!");
			else
				m_page->SetOutputText("GAME OVER - P2 wins!");
			break;
	}

	/// UPDATE ALL RENDER OBJECTS
	for(GameObject^ object : m_renderObjects)
	{
		if(object->Active())
			(object)->Update(this);
	}

	for(GameObject^ object : m_transparentRenderObjects)
	{
		if(object->Active())
			(object)->Update(this);
	}

	// UPDATE CAMERA
	if(m_camera != nullptr)
		m_camera->Update(this);
}

void GameEngine::HandleGamePlaying()
{
	HandlePowerupTiles();
	DrawPaddlePower(m_paddle1);
	DrawPaddlePower(m_paddle2);

	UpdatePucks();

	if(m_player == Player::One)
		HandleMusicTransition(m_player2Score);
	else if(m_player == Player::Two)
		HandleMusicTransition(m_player1Score);

	if(m_player1Score >= GameConstants::MaxScore || m_player2Score >= GameConstants::MaxScore)
		SetGameState(GameState::GameResults);
}

void GameEngine::HandlePowerupTiles()
{
	if(m_player == Player::One)
	{
		if(!m_splitActive)
		{
			if(m_timer->PlayingTime() - m_powerupTimer > GameConstants::Powerup::Split::Time)
			{
				for(WallTile^ tile : m_currentSplitTiles)
					tile->SetPowerup(Powerup::None);
				m_currentSplitTiles.clear();

				m_powerupTimer = m_timer->PlayingTime();

				for(int i = 0; i < GameConstants::Powerup::Split::NumTiles; i++)
				{
					int segmentIndex, tileIndex;

					do
					{
						// random segment, but every surface gets a tile
						segmentIndex = (rand() % 4) + 2; // constrain to middle panels (23456)
						tileIndex = i * 2; // 0246
					}
					while(m_wallTiles[segmentIndex][tileIndex]->GetPowerup() != Powerup::None);

					m_segments[i] = segmentIndex;

					WallTile^ tile = m_wallTiles[segmentIndex][tileIndex];
					tile->SetPowerup(Powerup::Split);
					m_currentSplitTiles.push_back(tile);
				}
			}
		}
	}
	else if(m_player == Player::Two)
	{
		if(m_timer->PlayingTime() - m_powerupTimer > GameConstants::Powerup::Split::Time)
		{
			for(WallTile^ tile : m_currentSplitTiles)
				tile->SetPowerup(Powerup::None);
			m_currentSplitTiles.clear();

			m_powerupTimer = m_timer->PlayingTime();
		}

		if(m_segments[0] == 0)
			ClearWallTiles();
		else
		{
			for(int i = 0; i < GameConstants::Powerup::Split::NumTiles; i++)
			{
				WallTile^ tile = m_wallTiles[m_segments[i]][i*2];
				tile->SetPowerup(Powerup::Split);
				m_currentSplitTiles.push_back(tile);
			}
		}
	}
}

void GameEngine::HandleNetwork()
{
	if(m_controllerListener1 != nullptr && m_controllerListener1->Connected)
	{
		if(m_lastPacket1 != m_controllerListener1->LastPacket)
		{
			UserInputPacket^ uip = dynamic_cast<UserInputPacket^>(m_controllerListener1->LastPacket);
			HandleControllerInput(uip, Player::One);
			m_lastPacket1 = m_controllerListener1->LastPacket;		
		}
	}

	if(m_controllerListener2 != nullptr && m_controllerListener2->Connected)
	{
		if(m_lastPacket2 != m_controllerListener2->LastPacket)
		{
			UserInputPacket^ uip = dynamic_cast<UserInputPacket^>(m_controllerListener2->LastPacket);
			HandleControllerInput(uip, Player::Two);
			m_lastPacket2 = m_controllerListener2->LastPacket;
		}
	}

	// server
	if(m_player == Player::One)
	{
		if(m_consoleListener != nullptr && m_consoleListener->Connected)
		{
			create_task([=]()
			{
				GameStatePacket^ gsp = CreateGameStatePacket();
				return m_consoleListener->WritePacketAsync(gsp);
			}).then([=](task<void> t)
			{
				try
				{
					t.get();
				}
				catch(Exception^ ex)
				{
					OutputDebugString(ex->Message->Data());
				}
			});
		}
	}
	// client
	else if(m_player == Player:: Two)
	{
		if(m_consoleClient != nullptr)
		{
			GameStatePacket^ gsp = dynamic_cast<GameStatePacket^>(m_consoleClient->LastPacket);
			if(gsp != nullptr)
			{
				m_pucks[0]->Position(XMFLOAT3(gsp->Puck1Position->X, gsp->Puck1Position->Y, gsp->Puck1Position->Z));
				m_pucks[1]->Position(XMFLOAT3(gsp->Puck2Position->X, gsp->Puck2Position->Y, gsp->Puck2Position->Z));
				m_pucks[2]->Position(XMFLOAT3(gsp->Puck3Position->X, gsp->Puck3Position->Y, gsp->Puck3Position->Z));

				m_pucks[0]->Active(gsp->Puck1Active);
				m_pucks[1]->Active(gsp->Puck2Active);
				m_pucks[2]->Active(gsp->Puck3Active);

				m_paddle1->NormalizedPosition(XMFLOAT3(gsp->Paddle1NormalizedPosition->X, gsp->Paddle1NormalizedPosition->Y, gsp->Paddle1NormalizedPosition->Z));
				m_paddle2->NormalizedPosition(XMFLOAT3(gsp->Paddle2NormalizedPosition->X, gsp->Paddle2NormalizedPosition->Y, gsp->Paddle2NormalizedPosition->Z));

				m_player1Score = gsp->Player1Score;
				m_player2Score = gsp->Player2Score;

				m_segments[0] = gsp->SplitSegment1;
				m_segments[1] = gsp->SplitSegment2;
				m_segments[2] = gsp->SplitSegment3;
				m_segments[3] = gsp->SplitSegment4;

				SetGameState(gsp->GameState);
			}
		}
	}
}

void GameEngine::HandleMusicTransition(int score)
{
	if(score >= GameConstants::HighScore && m_audioEngine->IsPlaying(SoundCue::Gameplay2))
	{
		m_audioEngine->StopSound(SoundCue::Gameplay2, false);
		m_audioEngine->PlaySound(SoundCue::Gameplay3, GameConstants::Audio::MusicVolume, true);
	}
	else if(score >= GameConstants::MediumScore && m_audioEngine->IsPlaying(SoundCue::Gameplay1))
	{
		m_audioEngine->StopSound(SoundCue::Gameplay1, false);
		m_audioEngine->PlaySound(SoundCue::Gameplay2, GameConstants::Audio::MusicVolume, true);
	}
}

void GameEngine::HandleControllerInput(UserInputPacket^ packet, Player player)
{
	if(packet != nullptr)
	{
		switch(packet->Command)
		{
			case UserInputCommand::StartGame:
				if(player == Player::One)
					m_player1Ready = true;
				else if(player == Player::Two)
					m_player2Ready = true;
				break;

			case UserInputCommand::Position:
				if ( m_gameState == GameState::GamePlaying )
				{
					Paddle^ paddle = nullptr;

					if(player == Player::One)
						paddle = m_paddle1;
					else if(player == Player::Two)
						paddle = m_paddle2;

					paddle->NormalizedPosition(XMFLOAT3(packet->Position->X, packet->Position->Y, packet->Position->Z));
				}
				break;
		}
	}
}

void GameEngine::FirePuck(int puckNum, Player player)
{
	if(puckNum >= GameConstants::MaxPucks)
		return;

	Paddle^ paddle = (player == Player::One ? m_paddle1 : m_paddle2);
	float center = (GameConstants::MaxBound.x - GameConstants::MinBound.x) /2.0f;

	XMFLOAT3 initialVelocity;
	if(paddle->Position().x < center)
		initialVelocity.x = 0.0f; //GameConstants::MaxPuckSpeed/5.5f;
	else
		initialVelocity.x = 0.0f; //-GameConstants::MaxPuckSpeed/5.5f;

	initialVelocity.y = 0.0f;
	initialVelocity.z = 20.0f * paddle->Normal().z;


	Puck^ puck = m_pucks[puckNum];
	puck->Active(true);
	puck->Attached(false);
	puck->Velocity(initialVelocity);
}

void GameEngine::AttachPuck(int puckNum, Player player)
{
	m_puckAttachedTo = player;

	Puck^ puck = m_pucks[puckNum];
	puck->Active(true);
	puck->Attached(true);
	puck->Velocity(XMFLOAT3(0,0,0));

	m_launchTimer = m_timer->PlayingTime();
}

void GameEngine::ResetPucks()
{
	for(Puck^ puck : m_pucks)
	{
		puck->Velocity(XMFLOAT3(0,0,0));
		puck->Position(XMFLOAT3(0,0,0));
		puck->OldPosition(XMFLOAT3(0,0,0));
		puck->Active(false);
		puck->Attached(false);
	}
}

Player GameEngine::GetPlayer()
{
	Player player = Player::Invalid;
	IVectorView<HostName^>^ hosts = NetworkInformation::GetHostNames();

	for(unsigned int i = 0; i < hosts->Size; i++)
	{
		HostName^ hn = hosts->GetAt(i);
		if(hn->CanonicalName == Settings::GameConsoleIp_Player1)
			player = Player::One;
		else if(hn->CanonicalName == Settings::GameConsoleIp_Player2)
			player = Player::Two;
	}

	return player;
}

void GameEngine::SetGameState(GameState gameState)
{
	if(m_gameState == gameState)
		return;

	m_gameState = gameState;

	switch(m_gameState)
	{
		case GameState::Initializing:
			break;
		case GameState::WaitingRoom:
			ResetGame();

			// clear the effects for the zoomed in mjpeg surface
			m_renderer->ApplyBloom(false);

			if(m_camera != nullptr)
			{
				if(m_player == Player::One)
					m_camera->AnimateEye(GameConstants::WaitingEyeP1, .09f);
				else if(m_player == Player::Two)
					m_camera->AnimateEye(GameConstants::WaitingEyeP2, .09f);
			}

			m_audioEngine->StopAllSounds();
			m_audioEngine->PlaySound(SoundCue::WaitingRoom, GameConstants::Audio::MusicVolume, true);

			m_hardware->WaitingRoom();
			break;
		case GameState::Countdown:
			ResetGame();

			// turn 'em back on
			m_renderer->ApplyBloom(true);

			m_paddle1->Active(true);
			m_paddle2->Active(true);

			m_timer->Start();

			if(m_camera != nullptr)
			{
				if(m_player == Player::One)
					m_camera->AnimateEye(XMFLOAT3(m_defaultEyePosition.x, m_defaultEyePosition.y, m_defaultEyePosition.z), .08f);
				else if(m_player == Player::Two)
					m_camera->AnimateEye(XMFLOAT3(m_defaultEyePosition.x, m_defaultEyePosition.y, m_defaultEyePosition.z), .08f);
			}

			m_audioEngine->StopAllSounds();
			m_audioEngine->PlaySound(SoundCue::Intro, GameConstants::Audio::MusicVolume, false);
			break;
		case GameState::GamePlaying:
			m_hardware->StartGame();

			m_pucks[GameConstants::MainPuck]->Active(true);
			m_pucks[GameConstants::MainPuck]->Position(XMFLOAT3(10.5f, 5.2f, GameConstants::MaxBound.z / 2.0f));
			m_pucks[GameConstants::MainPuck]->Velocity(XMFLOAT3(0.0f, 0.0f, -30.0f));

			m_audioEngine->StopSound(SoundCue::Intro, false);
			m_audioEngine->PlaySound(SoundCue::Gameplay1, GameConstants::Audio::MusicVolume, true);

			break;
		case GameState::GameResults:
			if(m_player1Score > m_player2Score)
			{
				m_winner = Player::One;
				m_hardware->EndGame(Player::One, Player::Two);
			}
			else if(m_player2Score > m_player1Score)
			{
				m_winner = Player::Two;
				m_hardware->EndGame(Player::Two, Player::One);
			}

			m_resetTimer = m_timer->PlayingTime();
			m_gameResultsAnimationTimer = m_timer->PlayingTime();

			m_segmentIndex = 1;
			m_direction = 1;

			ResetPucks();
			ClearWallTiles();
			TurnOffAllCountdownLights();

			if(!m_audioEngine->IsPlaying(SoundCue::Gameplay3))
			{
				m_audioEngine->StopAllSounds();
				m_audioEngine->PlaySound(SoundCue::Gameplay3, GameConstants::Audio::MusicVolume, true);
			}

			if((m_player == Player::One && m_winner == Player::One) || (m_player == Player::Two && m_winner == Player::Two))
				m_audioEngine->PlaySound(SoundCue::Victory, GameConstants::Audio::SfxVolume, false);

			if((m_player == Player::One && m_winner == Player::Two) || (m_player == Player::Two && m_winner == Player::One))
				m_audioEngine->PlaySound(SoundCue::Defeat, GameConstants::Audio::SfxVolume, false);
			break;
	}

	WriteStateToController(m_controllerListener1);
	WriteStateToController(m_controllerListener2);
}

GameStatePacket^ GameEngine::CreateGameStatePacket()
{
	auto gsp = ref new GameStatePacket();
	gsp->Puck1Position = ref new Point3(m_pucks[0]->Position().x, m_pucks[0]->Position().y, m_pucks[0]->Position().z);
	gsp->Puck2Position = ref new Point3(m_pucks[1]->Position().x, m_pucks[1]->Position().y, m_pucks[1]->Position().z);
	gsp->Puck3Position = ref new Point3(m_pucks[2]->Position().x, m_pucks[2]->Position().y, m_pucks[2]->Position().z);

	gsp->Puck1Active = m_pucks[0]->Active();
	gsp->Puck2Active = m_pucks[1]->Active();
	gsp->Puck3Active = m_pucks[2]->Active();

	gsp->Paddle1NormalizedPosition = ref new Point3(m_paddle1->NormalizedPosition().x, m_paddle1->NormalizedPosition().y, m_paddle1->NormalizedPosition().z);
	gsp->Paddle2NormalizedPosition = ref new Point3(m_paddle2->NormalizedPosition().x, m_paddle2->NormalizedPosition().y, m_paddle2->NormalizedPosition().z);

	gsp->Player1Score = m_player1Score;
	gsp->Player2Score = m_player2Score;

	gsp->GameState = m_gameState;

	gsp->SplitSegment1 = m_segments[0];
	gsp->SplitSegment2 = m_segments[1];
	gsp->SplitSegment3 = m_segments[2];
	gsp->SplitSegment4 = m_segments[3];

	return gsp;
}

void GameEngine::WriteStateToController(SocketListener^ listener)
{
	auto gsp = CreateGameStatePacket();

	if(listener != nullptr && listener->Connected)
	{
		create_task([=]()
		{
			return listener->WritePacketAsync(gsp);
		}).then([=](task<void> t)
		{
			try
			{
				t.get();
			}
			catch(Exception^ e)
			{
				OutputDebugString(e->Message->ToString()->Data());
			}
		});
	}
}

void GameEngine::HandleCountdown()
{
	DrawPaddlePower(m_paddle1);
	DrawPaddlePower(m_paddle2);

	float timeToPlay = 6.0f - m_timer->PlayingTime();

	if ( timeToPlay < 4.0 && m_countDownStep == 4 ) 
	{
		TurnOnCountdownLights(0);
		TurnOnCountdownLights(8);
		TurnOnCountdownLights(1);
		TurnOnCountdownLights(7);

		m_audioEngine->PlaySound(SoundCue::Three, GameConstants::Audio::SfxVolume, false);
		m_countDownStep = 3;
	}
	else if ( timeToPlay < 3.0 && m_countDownStep == 3 ) 
	{
		TurnOnCountdownLights(2);
		TurnOnCountdownLights(6);

		m_audioEngine->PlaySound(SoundCue::Two, GameConstants::Audio::SfxVolume, false);
		m_countDownStep = 2;
	}
	else if ( timeToPlay < 2.0 && m_countDownStep == 2 ) 
	{
		TurnOnCountdownLights(3);
		TurnOnCountdownLights(5);

		m_audioEngine->PlaySound(SoundCue::One, GameConstants::Audio::SfxVolume, false);
		m_countDownStep = 1;
	}
	else if ( timeToPlay < 1.0 && m_countDownStep == 1 )
	{
		TurnOnCountdownLights(4);

		m_audioEngine->PlaySound(SoundCue::Ignition, GameConstants::Audio::SfxVolume, false);
		m_countDownStep = 0;
		SetGameState(GameState::GamePlaying);
	}
}

void GameEngine::TurnOnCountdownLights(int index)
{
	m_wallTiles[index][1]->GetMaterial()->DiffusePercent(1.0f);
	m_wallTiles[index][3]->GetMaterial()->DiffusePercent(1.0f);
	m_wallTiles[index][5]->GetMaterial()->DiffusePercent(1.0f);
	m_wallTiles[index][7]->GetMaterial()->DiffusePercent(1.0f);

}

void GameEngine::TurnOffAllCountdownLights()
{
	for(unsigned int i = 0; i < m_wallTiles.size(); i++)
		TurnOffCountdownLights(i);
}

void GameEngine::TurnOffCountdownLights(int index)
{
	m_wallTiles[index][1]->GetMaterial()->DiffusePercent(0.0f);
	m_wallTiles[index][3]->GetMaterial()->DiffusePercent(0.0f);
	m_wallTiles[index][5]->GetMaterial()->DiffusePercent(0.0f);
	m_wallTiles[index][7]->GetMaterial()->DiffusePercent(0.0f);
}

void GameEngine::PulseCountdownLights(int index, XMFLOAT4 color, float fadeUp, float duration, float fadeDown)
{
	m_wallTiles[index][1]->PulseColor(color, fadeUp, duration, fadeDown, 2.0f, 0.5f);
	m_wallTiles[index][3]->PulseColor(color, fadeUp, duration, fadeDown, 2.0f, 0.5f);
	m_wallTiles[index][5]->PulseColor(color, fadeUp, duration, fadeDown, 2.0f, 0.5f);
	m_wallTiles[index][7]->PulseColor(color, fadeUp, duration, fadeDown, 2.0f, 0.5f);
}

void GameEngine::HandleGameResults()
{
	int middle = GameConstants::NumWallSegments / 2;

	float delta = m_timer->PlayingTime() - m_gameResultsAnimationTimer;
	if(delta > GameConstants::GameResults::Duration)
	{
		m_gameResultsAnimationTimer = m_timer->PlayingTime();
		if(m_winner == Player::One)
		{
			PulseCountdownLights(middle - m_segmentIndex, 
								GameConstants::GameResults::WinColor, 
								GameConstants::GameResults::FadeUpTime, 
								GameConstants::GameResults::Duration, 
								GameConstants::GameResults::FadeDownTime);
			PulseCountdownLights(middle + m_segmentIndex, 
								GameConstants::GameResults::LoseColor, 
								GameConstants::GameResults::FadeUpTime, 
								GameConstants::GameResults::Duration, 
								GameConstants::GameResults::FadeDownTime);
		}
		else if(m_winner == Player::Two)
		{
			PulseCountdownLights(middle - m_segmentIndex, 
								GameConstants::GameResults::LoseColor, 
								GameConstants::GameResults::FadeUpTime, 
								GameConstants::GameResults::Duration, 
								GameConstants::GameResults::FadeDownTime);
			PulseCountdownLights(middle + m_segmentIndex, 
								GameConstants::GameResults::WinColor, 
								GameConstants::GameResults::FadeUpTime, 
								GameConstants::GameResults::Duration, 
								GameConstants::GameResults::FadeDownTime);
		}

		m_segmentIndex += m_direction;

		if(m_segmentIndex == GameConstants::NumWallSegments/2 || m_segmentIndex == 1)
			m_direction *= -1;
	}
}

void GameEngine::ClearWallLights()
{
	for(std::vector<WallLight^> lights : m_wallLights)
	{
		for(WallLight^ light : lights)
		{
			light->EndPulse();
		}
	}
}

void GameEngine::ClearWallTiles()
{
	for(std::vector<WallTile^> tiles : m_wallTiles)
	{
		for(WallTile^ tile : tiles)
		{
			tile->GetMaterial()->DiffusePercent(1.0f);
			tile->SetPowerup(Powerup::None);
			tile->EndPulse();
		}
	}

	for(int i = 0; i < GameConstants::Powerup::Split::NumTiles; i++)
		m_segments[i] = 0;
}

void GameEngine::SplitPucks()
{
	Puck^ activePuck = m_pucks[GameConstants::MainPuck];

	if(m_splitActive)
		return;

	m_splitActive = true;

	ClearWallTiles();

	bool FirstPuck = true;

	for(Puck^ puck : m_pucks)
	{
		if(puck == activePuck)
		{
			XMFLOAT3 activeVelocity = XMFLOAT3(activePuck->Velocity());	
			if ( fabs(activeVelocity.z) < GameConstants::MinPuckSpeed) 
			{
				activeVelocity.z = GameConstants::MinPuckSpeed * activeVelocity.z/fabs(activeVelocity.z);
			}
			puck->Velocity(activeVelocity);
			continue;
		}

		puck->Active(true);

		XMFLOAT3 position = XMFLOAT3(activePuck->Position());
		puck->Position(position);

		XMFLOAT3 velocity = XMFLOAT3(activePuck->Velocity());
		velocity.x += (1.0f - (rand() / (float)RAND_MAX*2.0f)) * GameConstants::SplitPuckVelocityFactor;
		velocity.y += (1.0f - (rand() / (float)RAND_MAX*2.0f)) * GameConstants::SplitPuckVelocityFactor;
		velocity.z += (rand() / (float)RAND_MAX) * GameConstants::SplitPuckVelocityFactor * (fabs(velocity.z)/velocity.z);

		if ( fabs(velocity.z) < GameConstants::MinPuckSpeed) 
			velocity.z = GameConstants::MinPuckSpeed * velocity.z/fabs(velocity.z);

		puck->Velocity(velocity);
	}
}

void GameEngine::UpdatePucks() 
{
	if(m_splitActive && OnlyOnePuckActive())
	{
		m_splitActive = false;
		m_powerupTimer = -GameConstants::Powerup::Split::Time;

		for(Puck^ puck : m_pucks)
		{
			if(puck->Active() && puck != m_pucks[GameConstants::MainPuck])
			{
				m_pucks[GameConstants::MainPuck]->Position(puck->Position());
				m_pucks[GameConstants::MainPuck]->Velocity(puck->Velocity());
				m_pucks[GameConstants::MainPuck]->Active(true);
				puck->Active(false);
				break;
			}
		}
	}

	for(Puck^ puck : m_pucks)
	{
		if (!puck->Active())
			continue;

		if(puck->Attached())
		{
			if(m_timer->PlayingTime() - m_launchTimer > GameConstants::PuckLaunchTimer)
				FirePuck(GameConstants::MainPuck, m_puckAttachedTo);
			else
			{
				XMFLOAT3 pos;

				if(m_puckAttachedTo == Player::One)
				{
					pos = m_paddle1->Position();
					pos.z += 1.0f;
				}
				else if(m_puckAttachedTo == Player::Two)
				{
					pos = m_paddle2->Position();
					pos.z -= 1.0f;
				}

				puck->Position(pos);
			}
		}
		else
		{
			// CHECK NEW POSITION FOR WALL CONTACT
			bool bounce = CheckWallCollision(puck);

			// if we hit the wall, we couldn't have it the paddle, too
			if(!bounce)
			{
				// CHECK FOR PADDLE COLLISION
				bounce |= CheckPuckPaddleCollision(puck, m_paddle1);

				// if we hit paddle1, we can't also hit paddle 2
				if(!bounce)
				{
					bounce |= CheckPuckPaddleCollision(puck, m_paddle2);

					// if we hit the walls or the paddle, we can't hit the back wall, too
					if(!bounce)
					{
						// CHECK FOR GOAL WALL CONTACT
						XMFLOAT3 puckPosition = puck->Position();
						XMFLOAT3 puckVelocity = puck->Velocity();
						float radius = puck->Radius();

						// hit player 1?
						if ((puckPosition.z - radius) < GameConstants::MinBound.z)
						{
							puckPosition.z = GameConstants::MinBound.z + radius*4.0f;
							puckVelocity.z *= -1.0f;
							Score(Player::Two, puck);
						}

						// hit player 2?
						if ((puckPosition.z + radius) > GameConstants::MaxBound.z)
						{
							puckPosition.z = GameConstants::MaxBound.z - radius*4.0f;
							puckVelocity.z *= -1.0f;
							Score(Player::One, puck);
						}
						puck->Velocity(puckVelocity);
						puck->Position(puckPosition);
					}
				}
			}

			if(bounce)
				m_audioEngine->PlayBounce(1.0f);
		}
	}
}

bool GameEngine::AnyPucksActive()
{
	for(Puck^ puck : m_pucks)
	{
		if(puck->Active())
			return true;
	}

	return false;
}

bool GameEngine::OnlyOnePuckActive()
{
	bool oneActive = false;

	for(Puck^ puck : m_pucks)
	{
		if(puck->Active() && oneActive)
			return false;

		if(puck->Active())
			oneActive = true;
	}

	return true;
}

void GameEngine::Score(Player playerWhoScored, Puck^ puck)
{
	puck->Active(false);

	if(playerWhoScored == Player::One)
	{
		m_player1Score++;

		if(m_player == Player::One)
		{
			m_player2Camera->PulseColor(GameConstants::Damage::Color, GameConstants::Damage::FadeUpTime, GameConstants::Damage::Duration, GameConstants::Damage::FadeDownTime,
			GameConstants::Damage::ColorMaxPercent, GameConstants::Damage::DiffuseMinPercent);
		}
		else if(m_player == Player::Two)
		{
			m_damageTimer = m_timer->PlayingTime() + GameConstants::Damage::DamageDuration;
			m_camera->Shake(this);
		}

		m_hardware->Score(Player::One, Player::Two, (double)(m_player1Score / GameConstants::MaxScore));

		if(!AnyPucksActive())
			AttachPuck(GameConstants::MainPuck, Player::Two);
	}
	else if(playerWhoScored == Player::Two)
	{
		m_player2Score++;

		if(m_player == Player::One)
		{
			m_damageTimer = m_timer->PlayingTime() + GameConstants::Damage::DamageDuration;
			m_camera->Shake(this);
		}
		else if(m_player == Player::Two)
		{
			m_player1Camera->PulseColor(GameConstants::Damage::Color, GameConstants::Damage::FadeUpTime, GameConstants::Damage::Duration, GameConstants::Damage::FadeDownTime,
			GameConstants::Damage::ColorMaxPercent, GameConstants::Damage::DiffuseMinPercent);
		}

		m_hardware->Score(Player::Two, Player::One, (double)(m_player2Score / GameConstants::MaxScore));

		if(!AnyPucksActive())
			AttachPuck(GameConstants::MainPuck, Player::One);
	}

	m_audioEngine->PlaySound(SoundCue::Buzz, GameConstants::Audio::SfxVolume, false);

	WriteStateToController(m_controllerListener1);
	WriteStateToController(m_controllerListener2);
}

void GameEngine::DrawPaddlePower(Paddle^ paddle)
{
	float power = min(paddle->Power() / GameConstants::PaddlePower::Max, 1.0f);

	XMFLOAT4 color;
	color.x = GameConstants::PaddlePower::Color.x * power;
	color.y = GameConstants::PaddlePower::Color.y * power;
	color.z = GameConstants::PaddlePower::Color.z * power;
	color.w = 1.0f;

	paddle->GetMaterial()->MeshColor(color);
	paddle->GetMaterial()->MeshPercent(power * GameConstants::PaddlePower::MeshPercent);
}

bool GameEngine::CheckWallCollision(Puck^ puck) 
{
	bool isIntersect = false;
	bool wallCollision = false;

	//for(WallTile^ wall : m_environmentCollisionWalls)
	for(unsigned int i = 0; i < m_environmentCollisionWalls.size(); i++)
	{
		WallTile^ wall = m_environmentCollisionWalls[i];
		float radius = puck->Radius();
		float signedRadius = puck->Radius();
		float contactTime = 0.0f;
		XMVECTOR contactPlanePoint = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
		XMVECTOR contactPuckPosition = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);

		bool intersectsPlane = false;

		// Determine the velocity of this tick by subtracting the previous position from the proposed current position.
		// in the previous update() cycle, puck->Position() = puck->OldPosition() + ( puck->velocity * timerDelta ).
		// Therefore, this calculated velocity for the current frame movement differs from the stored velocity 
		// since the stored velocity is independent of each game tick's timerDelta.
		XMVECTOR puckVectorVelocity = puck->VectorPosition() - puck->OldVectorPosition();
	
		// The D for the wall is the distance from z=0, since the normal is [ 0 0 1]
		float D = XMVectorGetX( XMVector3Dot( wall->VectorNormal(), wall->VectorPosition() ) );
	
		// Determine the distance of the puck to the plane of the wall.
		float dist = XMVectorGetX( XMVector3Dot(wall->VectorNormal(), puck->OldVectorPosition() )) - D;
		signedRadius = dist > 0 ? radius : -radius;

		// if the distance of the puck to the plane is already less than the radius, the oldPosition() was intersecting already
		if ( fabs(dist) < radius )
		{
			// The sphere is touching the plane.
			intersectsPlane = true;
			contactTime = 0.0f;
			contactPuckPosition = puck->OldVectorPosition();
			contactPlanePoint = puck->OldVectorPosition() + wall->VectorNormal()*XMVectorSet(signedRadius,signedRadius,signedRadius,1.0f);
		}
		else
		{
			// See if the time it would take to cross the plane from the oldPosition() with the current velocity falls within this game tick.
			// puckVelocityNormal is the amount of force from the velocity exerted directly toward the plane.
			float puckVelocityNormal = XMVectorGetX(XMVector3Dot(wall->VectorNormal(), puckVectorVelocity ));
			// if the puckvVelocityNormal times the distance is less than zero, a plane intersection will occur
			if ( puckVelocityNormal * dist < 0.0f ) 
			{
				// determine the contactTime, taking into account the shell of the sphere ( position() + radius ) 
				// is what will make contact, not the position alone.
				contactTime =  (signedRadius - dist) / puckVelocityNormal;
				// if the contact time is bewteen zero and one, the intersection has occured bewteen oldPosition() and position()
				if ( contactTime > 0.0f && contactTime < 1.0f )
				{
					intersectsPlane = true;
					// this is the position of the puck when its shell makes contact on the plane
					contactPuckPosition = puck->OldVectorPosition() + XMVectorScale(puckVectorVelocity, contactTime);
					// this is the position on the plane where the shell touches.
					contactPlanePoint = contactPuckPosition - XMVectorScale(wall->VectorNormal(), signedRadius);
				}
			}
		}

		// If the puck has contacted the wall plane, determine if the point of contact falls within the wall boundary for true contact.
		if (intersectsPlane)
		{
			float Kr = 1.0f; // Kr is the coefficient of restitution. At 1.0, we have a totally elastic bounce with no dampening. At Kr = 0.0, the ball would stop at the wall.

			// Make sure the puck velocity and wall normal are facing each other
			float impact = XMVectorGetX ( XMVector3Dot ( wall->VectorNormal(), puck->VectorVelocity()) );
			if (impact < 0.0f)
			{
				wallCollision = true;

				//// bounce the vector off the plane
				XMVECTOR VectorNormal = XMVector3Dot(wall->VectorNormal(), puck->VectorVelocity())*wall->VectorNormal();
				XMVECTOR VectorTangent = puck->VectorVelocity() - VectorNormal;

				//// Compute post-collision velocity of the puck.
				//float testZ = puck->Velocity().z;
				//if ( fabs(testZ) < GameConstants::MinPuckSpeed ) 
				//{
				//	testZ = GameConstants::MinPuckSpeed*(fabs(testZ)/testZ);
				//	puck->Velocity(XMFLOAT3(puck->Velocity().x, puck->Velocity().y, testZ));
				//}
				//puck->Velocity(XMFLOAT3( puck->Velocity().x, puck->Velocity().y, testZ));
				
				puck->Velocity(VectorTangent - (XMVectorScale(VectorNormal, Kr)));
				puck->Position(contactPuckPosition);

				int segment = (int)(puck->Position().z / GameConstants::WallSegmentDepth);
				segment = max(min(segment, GameConstants::NumWallSegments-1), 0);

				auto tiles = m_wallTiles[segment];
				WallTile^ tile = tiles[i];
				if(tile->GetPowerup() == Powerup::Split)
					SplitPucks();

				//tile->PulseColor(XMFLOAT4(0,0,1.0f,0.5f), 0.25f, 0.25f, 0.25f);

				break;
			}
		}
	}

	return wallCollision;
}

bool GameEngine::CheckPuckPaddleCollision(Puck^ puck, Paddle^ paddle)
{
	float radius = puck->Radius();
	float signedRadius = puck->Radius();
	float contactTime = 0.0f;
	XMVECTOR contactPlanePoint = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
	XMVECTOR contactPuckPosition = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);

	bool intersectsPlane = false;
	bool paddleCollision = false;

	// Determine the velocity of this tick by subtracting the previous position from the proposed current position.
	// in the previous update() cycle, puck->Position() = puck->OldPosition() + ( puck->velocity * timerDelta ).
	// Therefore, this calculated velocity for the current frame movement differs from the stored velocity 
	// since the stored velocity is independent of each game tick's timerDelta.
	XMVECTOR puckVectorVelocity = puck->VectorPosition() - puck->OldVectorPosition();
	
	// The D for the paddle is the distance from z=0, since the normal is [ 0 0 1]
	float D = XMVectorGetX( XMVector3Dot( paddle->VectorNormal(), paddle->VectorPosition() ) );
	
	// Determine the distance of the puck to the plane of the paddle.
	float dist = XMVectorGetX( XMVector3Dot(paddle->VectorNormal(), puck->OldVectorPosition() )) - D;
	signedRadius = dist > 0 ? radius : -radius;

	// if the distance of the puck to the plane is already less than the radius, the oldPosition() was intersecting already
	if ( fabs(dist) < radius )
	{
		// The sphere is touching the plane.
		intersectsPlane = true;
		contactTime = 0.0f;
		contactPuckPosition = puck->OldVectorPosition();
		contactPlanePoint = puck->OldVectorPosition() + paddle->VectorNormal()*XMVectorSet(signedRadius,signedRadius,signedRadius,1.0f);
	}
	else
	{
		// See if the time it would take to cross the plane from the oldPosition() with the current velocity falls within this game tick.
		// puckVelocityNormal is the amount of force from the velocity exerted directly toward the plane.
		float puckVelocityNormal = XMVectorGetX(XMVector3Dot(paddle->VectorNormal(), puckVectorVelocity ));
		// if the puckvVelocityNormal times the distance is less than zero, a plane intersection will occur
		if ( puckVelocityNormal * dist < 0.0f ) 
		{
			// determine the contactTime, taking into account the shell of the sphere ( position() + radius ) 
			// is what will make contact, not the position alone.
			contactTime =  (signedRadius - dist) / puckVelocityNormal;
			// if the contact time is bewteen zero and one, the intersection has occured bewteen oldPosition() and position()
			if ( contactTime > 0.0f && contactTime < 1.0f )
			{
				intersectsPlane = true;
				// this is the position of the puck when its shell makes contact on the plane
				contactPuckPosition = puck->OldVectorPosition() + XMVectorScale(puckVectorVelocity, contactTime);
				// this is the position on the plane where the shell touches.
				contactPlanePoint = contactPuckPosition - XMVectorScale(paddle->VectorNormal(), signedRadius);
			}
		}
	}

	// If the puck has contacted the paddle plane, determine if the point of contact falls within the paddle boundary for true contact.
	if (intersectsPlane)
	{
		XMFLOAT3 contactFloat;
		XMStoreFloat3(&contactFloat, contactPlanePoint);

		float dx = (contactFloat.x - paddle->Position().x) * paddle->Normal().z;
		float dy = (contactFloat.y - paddle->Position().y) * paddle->Normal().z;  
		float paddleSize = 2.5f; // this should be paddle->width() + puck->radius();
		float Kr = 1.0; // Kr is the coefficient of restitution. At 1.0, we have a totally elastic bounce with no dampening. At Kr = 0.0, the ball would stop at the paddle.

		// No use squaringRooting both sides, simply compare the squares to see if distance of the
		// paddle position is less than the maximum radius of the paddle.
		if ( (dx*dx + dy*dy) < ( paddleSize*paddleSize) )
		{
			// Make sure the puck velocity and paddle normal are facing each other
			float impact = XMVectorGetX ( XMVector3Dot ( paddle->VectorNormal(), puck->VectorVelocity()) );
			if (impact < 0.0f)
			{
				paddleCollision = true;

				// ADD POLISH BASED ON HOW CLOSE TO THE EDGE OF THE PADDLE THEY HIT THE PUCK
				XMVECTOR PolishNormal = XMVectorSet(dx/(paddleSize*4), dy/(paddleSize*4), 1.0f, 1.0f);

				// bounce the vector off the plane
				XMVECTOR VectorNormal = XMVector3Dot(PolishNormal, puck->VectorVelocity())*PolishNormal;
				XMVECTOR VectorTangent = puck->VectorVelocity() - VectorNormal;
				// Compute post-collision velocity of the puck.
				puck->Velocity(VectorTangent - (XMVectorScale(VectorNormal, Kr)));

				float testZ = puck->Velocity().z + (paddle->Power()*paddle->Normal().z);
				if ( fabs(testZ) < GameConstants::MinPuckSpeed ) 
				{
					testZ = GameConstants::MinPuckSpeed*(fabs(testZ)/testZ);
				}

				puck->Velocity(XMFLOAT3( puck->Velocity().x, puck->Velocity().y, testZ));
				puck->Position(contactPuckPosition);
				paddle->Power(GameConstants::PaddlePower::Max);
			}
		}
	}

	return paddleCollision;
}

void GameEngine::MjpegStation(bool enabled)
{
	m_mjpegStation = enabled;
	m_camera->AnimateEye(GameConstants::WaitingEyeMjpegStation, 0.18f);
}
